#pragma once

#include <vex/iterable/contract/range.h>
#include <vex/iterable/range_traits.h>
#include <vex/iterable/concept/range_concepts.h>
#include <vex/core/make_raw_handle.h>

namespace vex { namespace iterable { namespace implementation {
    namespace detail {
        template <class TRange, class TCategory, class TRef, class TRangeContract> 
        class range_default_impl_base;

        template<class TRange, class TRangeContract>
        class VEX_ABSTRACT range_default_impl_base<TRange, trivial_tag, void, TRangeContract> 
            : public TRangeContract 
        {
            range_default_impl_base(range_default_impl_base const &);
            range_default_impl_base& operator=(range_default_impl_base const &);
        protected:
            typedef typename range_traits<TRange> traits;
            typedef typename traits::range_type range_type;
            BOOST_CONCEPT_ASSERT((concept::TrivialRange<range_type>));

            range_type m_range;

            template<class URange>
            range_default_impl_base(URange&& p_range)
                : m_range(std::forward<URange>(p_range)) {}

            range_default_impl_base(range_default_impl_base&& p_other)
                : m_range(std::move(p_other.m_range)) {}

            ~range_default_impl_base() {}
        public:
            virtual bool move_front()
            {
                return this->m_range.move_front();
            }
        };

        template<class TRange, class TRef, class TRangeContract>
        class VEX_ABSTRACT range_default_impl_base<TRange, output_tag, TRef, TRangeContract>
            : public range_default_impl_base<TRange, trivial_tag, void, TRangeContract>
        {
        protected:
            typedef typename range_traits<TRange> traits;
            typedef typename traits::range_type range_type;
            typedef range_default_impl_base<TRange, trivial_tag, void, TRangeContract> base_type;
            BOOST_CONCEPT_ASSERT((concept::OutputRange<range_type, TRef>));

            template<class URange>
            range_default_impl_base(URange&& p_range)
                : base_type(std::forward<URange>(p_range)) {}

            range_default_impl_base(range_default_impl_base&& p_other)
                : m_range(std::move(p_other)) {}

            ~range_default_impl_base() {}
        public:
            virtual bool put(TRef p_value)
            {
                return this->m_range.put(std::forward<TRef>(p_value));
            }
        };

        template<class TRange, class TRef, class TRangeContract> 
        class VEX_ABSTRACT range_default_impl_base<TRange, one_pass_tag, TRef, TRangeContract>
            : public range_default_impl_base<TRange, trivial_tag, void, TRangeContract>
        {
        protected:
            typedef typename range_traits<TRange> traits;
            typedef typename traits::range_type range_type;
            typedef range_default_impl_base<TRange, trivial_tag, void, TRangeContract> base_type;
            BOOST_CONCEPT_ASSERT((concept::OnePassRange<range_type>));

            template<class URange>
            range_default_impl_base(URange&& p_range)
                : base_type(std::forward<URange>(p_range)) {}

            range_default_impl_base(range_default_impl_base&& p_other)
                : m_range(std::move(p_other)) {}

            ~range_default_impl_base() {}
        public:
            virtual TRef front() const
            {
                return this->m_range.front();
            }
        };

        template<class TRange, class TRef, class TRangeContract>
        class VEX_ABSTRACT range_default_impl_base<TRange, forward_tag, TRef, TRangeContract>
            : public range_default_impl_base<TRange, one_pass_tag, TRef, TRangeContract>
        {
        protected:
            typedef typename range_traits<TRange> traits;
            typedef typename traits::range_type range_type;
            typedef range_default_impl_base<TRange, one_pass_tag, TRef, TRangeContract> base_type;
            BOOST_CONCEPT_ASSERT((concept::ForwardRange<range_type>));

            template<class URange>
            range_default_impl_base(URange&& p_range)
                : base_type(std::forward<URange>(p_range)) {}

            range_default_impl_base(range_default_impl_base&& p_other)
                : m_range(std::move(p_other)) {}

            ~range_default_impl_base() {}
        };

        template<class TRange, class TRef, class TRangeContract>
        class VEX_ABSTRACT range_default_impl_base<TRange, bidirectional_tag, TRef, TRangeContract>
            : public range_default_impl_base<TRange, forward_tag, TRef, TRangeContract>
        {
        protected:
            typedef typename range_traits<TRange> traits;
            typedef typename traits::range_type range_type;
            typedef range_default_impl_base<TRange, forward_tag, TRef, TRangeContract> base_type;
            BOOST_CONCEPT_ASSERT((concept::BiDirectionalRange<range_type>));

            template<class URange>
            range_default_impl_base(URange&& p_range)
                : base_type(std::forward<URange>(p_range)) {}

            range_default_impl_base(range_default_impl_base&& p_other)
                : m_range(std::move(p_other)) {}

            ~range_default_impl_base() {}
        public:
            virtual TRef back() const
            {
                return this->m_range.back();
            }

            virtual bool move_back()
            {
                return this->m_range.move_back();
            }
        };

        template<class TRange, class TRef, class TRangeContract>
        class VEX_ABSTRACT range_default_impl_base<TRange, random_access_tag, TRef, TRangeContract>
            : public range_default_impl_base<TRange, bidirectional_tag, TRef, TRangeContract>
        {
        protected:
            typedef typename range_traits<TRange> traits;
            typedef typename traits::range_type range_type;
            typedef range_default_impl_base<TRange, bidirectional_tag, TRef, TRangeContract> base_type;
            BOOST_CONCEPT_ASSERT((concept::RandomAccessRange<range_type>));

            template<class URange>
            range_default_impl_base(URange&& p_range)
                : base_type(std::forward<URange>(p_range)) {}

            range_default_impl_base(range_default_impl_base&& p_other)
                : m_range(std::move(p_other)) {}

            ~range_default_impl_base() {}
        public:
            virtual bool move_front_of(std::size_t p_offset)
            {
                return this->m_range.move_front_of(p_offset);
            }

            virtual bool move_back_of(std::size_t p_offset)
            {
                return this->m_range.move_back_of(p_offset);
            }

            virtual TRef at(std::size_t p_offset) const
            {
                return this->m_range.at(p_offset);
            }

            virtual std::size_t size() const
            {
                return this->m_range.size();
            }
        };
    }

    template<
        class TRange, 
        class TCategory = typename range_traits<TRange>::category, 
        class TRef = typename range_traits<TRange>::reference,
        class TRangeContract = contract::range<TCategory, TRef>,
        bool IsConvertibleToForward = std::is_convertible<TCategory, forward_tag>::value
    >
    class VEX_ABSTRACT range_default_impl 
        : public detail::range_default_impl_base<TRange, TCategory, TRef, TRangeContract> 
    {
    protected:
        typedef typename range_traits<TRange> traits;
        typedef typename traits::range_type range_type;
        typedef detail::range_default_impl_base<TRange, TCategory, TRef, TRangeContract> base_type;

        template<class URange>
        explicit range_default_impl(URange&& p_range, typename std::enable_if<
            std::is_convertible<URange, range_type>::value>::type* = 0)
            : base_type(std::forward<URange>(p_range)) {}

        range_default_impl(range_default_impl&& p_other)
            : base_type(std::move(p_other)) {}

        ~range_default_impl() {}
    public:
        range_default_impl* save() const
        {
            return core::make_raw_handle<range_default_impl>(this->m_range.save());
        }
    };

    template<
        class TRange,
        class TCategory,
        class TRef,
        class TRangeContract
    >
    class VEX_ABSTRACT range_default_impl<TRange, TCategory, TRef, TRangeContract, false>
        : public detail::range_default_impl_base<TRange, TCategory, TRef, TRangeContract> 
    {
    protected:
        typedef typename range_traits<TRange> traits;
        typedef typename traits::range_type range_type;
        typedef detail::range_default_impl_base<TRange, TCategory, TRef, TRangeContract> base_type;

        template<class URange>
        explicit range_default_impl(URange&& p_range, typename std::enable_if<
            std::is_convertible<URange, range_type>::value>::type* = 0)
            : base_type(std::forward<URange>(p_range)) {}

        range_default_impl(range_default_impl&& p_other)
            : base_type(std::move(p_other)) {}

        ~range_default_impl() {}
    };
}}}